﻿using IV.Core.Enums;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;

namespace IV.Core
{
    public class AppPermissionService
    {
        public static event Action PermissionUpdateEvent;

        private static readonly Dictionary<ModuleItem, List<PermissionItem>> _permissionDict = new Dictionary<ModuleItem, List<PermissionItem>>();

        public static void RegisterPermission<T>(PermissionItem permissionItem, string moduleDiscribe = "")
        {
            RegisterPermission(typeof(T).Name, permissionItem, moduleDiscribe);
        }

        public static void RegisterPermission(string moduleName, PermissionItem permissionItem, string moduleDiscribe = "")
        {
            if (string.IsNullOrWhiteSpace(moduleName))
                return;
            if (string.IsNullOrWhiteSpace(moduleDiscribe))
                moduleDiscribe = moduleName;
            if (!IsExitModule(moduleName))
                _permissionDict.Add(new ModuleItem(moduleName, moduleDiscribe), new List<PermissionItem>());
            if (!IsExitPermission(moduleName, permissionItem.ItemName))
            {
                foreach (var item in _permissionDict)
                    if (item.Key.ItemName.Equals(moduleName))
                        _permissionDict[item.Key].Add(permissionItem);
            }
        }

        public static void RegisterPermission<T>(List<PermissionItem> permissionItems, string moduleDiscribe = "")
        {
            foreach (var item in permissionItems)
            {
                RegisterPermission(typeof(T).Name, item, moduleDiscribe);
            }
        }

        public static void RegisterPermission(string moduleName, List<PermissionItem> permissionItems, string moduleDiscribe = "")
        {
            foreach (var item in permissionItems)
            {
                RegisterPermission(moduleName, item, moduleDiscribe);
            }
        }

        public static void SetPermission<T>(string permissionName, bool isEnable)
        {
            SetPermission(typeof(T).Name, permissionName, isEnable);
        }

        public static void SetPermission(string moduleName, string permissionName, bool isEnable)
        {
            if (string.IsNullOrWhiteSpace(moduleName))
                return;
            var permission = GetLocalPermission(moduleName, permissionName);
            if (permission != null)
                permission.IsEnable = isEnable;
        }

        public static bool HavePermission<T>(string permissionName)
        {
            return HavePermission(typeof(T).Name, permissionName);
        }

        public static bool HavePermission(string moduleName, string permissionName)
        {
            if (string.IsNullOrWhiteSpace(moduleName))
                return false;
            var permission = GetLocalPermission(moduleName, permissionName);
            if (permission != null)
                return permission.IsEnable;
            return false;
        }

        public static List<PermissionItem> GetPermissions<T>()
        {
            return Clone(GetLocalPermissions(typeof(T).Name));
        }

        public static List<PermissionItem> GetPermissions(string moduleName)
        {
            return Clone(GetLocalPermissions(moduleName));
        }

        public static PermissionItem GetPermission<T>(string permissionName)
        {
            var result = GetLocalPermission(typeof(T).Name, permissionName);
            if(result == null)
                return new PermissionItem();
            return Clone(result);
        }

        public static PermissionItem GetPermission(string moduleName, string permissionName)
        {
            return Clone(GetLocalPermission(moduleName, permissionName));
        }

        public static Dictionary<ModuleItem, List<PermissionItem>> GetAllPermissionStatus()
        {
            return Clone(_permissionDict);
        }

        public static void LoadPermissions(Dictionary<ModuleItem, List<PermissionItem>> permissions)
        {
            foreach (var tempPermissions in _permissionDict)
                foreach (var permission in tempPermissions.Value)
                    permission.IsEnable = false;

            foreach (var item in permissions)
            {
                var permissionList = GetLocalPermissions(item.Key.ItemName);
                if (permissionList != null)
                {
                    foreach (var tempPermission in item.Value)
                    {
                        var permission = GetLocalPermission(item.Key.ItemName, tempPermission.ItemName);
                        if (permission != null)
                            SetPermission(item.Key.ItemName, tempPermission.ItemName, tempPermission.IsEnable);
                    }
                }
            }
            PermissionUpdateEvent?.Invoke();
        }

        public static void ResetAllPermissions()
        {
            foreach(var permissions in _permissionDict)
                foreach(var permission in permissions.Value)
                    permission.IsEnable = false;
            PermissionUpdateEvent?.Invoke();
        }

        private static List<PermissionItem> GetLocalPermissions(string moduleName)
        {
            if (string.IsNullOrWhiteSpace(moduleName))
                return null;
            if (!IsExitModule(moduleName))
                return null;
            foreach (var item in _permissionDict)
                if (item.Key.ItemName.Equals(moduleName))
                    return item.Value;
            return null;
        }

        private static PermissionItem GetLocalPermission(string moduleName, string permissionName)
        {
            if (string.IsNullOrWhiteSpace(moduleName))
                return null;
            if (!IsExitModule(moduleName))
                return null;
            var permissions = GetLocalPermissions(moduleName);
            if (permissions == null)
                return null;

            foreach (var permission in permissions)
                if (permission.ItemName.Equals(permissionName))
                    return permission;
            return null;
        }

        private static bool IsExitModule(string moduleName)
        {
            if (string.IsNullOrWhiteSpace(moduleName))
                return false;
            foreach (var item in _permissionDict)
            {
                if (item.Key.ItemName.Equals(moduleName))
                    return true;
            }
            return false;
        }

        private static bool IsExitPermission(string moduleName, string permissionName)
        {
            if (string.IsNullOrWhiteSpace(moduleName))
                return false;
            if (!IsExitModule(moduleName))
                return false;
            if (GetLocalPermission(moduleName, permissionName) != null)
                return true;
            return false;
        }

        private static T Clone<T>(T obj)
        {
            MemoryStream memoryStream = new MemoryStream();
            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(memoryStream, obj);
            memoryStream.Position = 0;
            return (T)formatter.Deserialize(memoryStream);
        }
    }

    public class PermissionLocal
    {
        public string NeedPermission { get; set; }

        public bool IsEnable<T>()
        {
            return AppPermissionService.HavePermission<T>(NeedPermission);
        }

        public bool IsEnable<T>(string permission)
        {
            return AppPermissionService.HavePermission<T>(permission);
        }

        public string PermissionPrompt<T>()
        {
            return AppPermissionService.GetPermission<T>(NeedPermission).ItemPrompt;
        }

        public bool IsEnable(string moduleName)
        {
            return AppPermissionService.HavePermission(moduleName, NeedPermission);
        }
    }

    public interface IPermissionBase
    {
        PermissionLocal PermissionLocal { get; }
    }

    public interface IPermissionInfPersistence
    {
        public void Initialize();

        public Dictionary<ModuleItem, List<PermissionItem>> LoadPermissions(EUserLevel eUserLevel);

        public void SavePermissions(Dictionary<ModuleItem, List<PermissionItem>> permissions,EUserLevel eUserLevel);
    }

    public class XmlPermissionInfPersistence : IPermissionInfPersistence
    {
        public string SavePath { get; set; } = Path.Combine(Environment.CurrentDirectory, "Permissions");
        public void Initialize()
        {
            Directory.CreateDirectory(SavePath);
        }

        public Dictionary<ModuleItem, List<PermissionItem>> LoadPermissions(EUserLevel eUserLevel)
        {
            var path = Path.Combine(SavePath, $"{eUserLevel}Permission.xml");
            if (!File.Exists(path))
                SavePermissions(AppPermissionService.GetAllPermissionStatus(), eUserLevel);
            Stream stream = File.OpenRead(path);
            SerializableDictionary<ModuleItem, List<PermissionItem>> serializablePermissionDic = new SerializableDictionary<ModuleItem, List<PermissionItem>>();
            serializablePermissionDic.ReadXml(XmlReader.Create(stream));
            return serializablePermissionDic;
        }

        public void SavePermissions(Dictionary<ModuleItem, List<PermissionItem>> permissions,EUserLevel eUserLevel)
        {
            SerializableDictionary<ModuleItem, List<PermissionItem>> serializablePermissionDic = new SerializableDictionary<ModuleItem, List<PermissionItem>>(permissions);
            serializablePermissionDic.WriteXml(new XmlTextWriter(Path.Combine(SavePath, $"{eUserLevel}Permission.xml"), Encoding.UTF8));
        }
    }

    public class EncryptedPermissionInfPersistence : IPermissionInfPersistence
    {
        public string SavePath { get; set; } = Path.Combine(Environment.CurrentDirectory, "Permissions");
        public void Initialize()
        {
            Directory.CreateDirectory(SavePath);
        }

        public Dictionary<ModuleItem, List<PermissionItem>> LoadPermissions(EUserLevel eUserLevel)
        {
            var path = Path.Combine(SavePath, $"{eUserLevel}Permission.bin");
            if (!File.Exists(path))
                SavePermissions(AppPermissionService.GetAllPermissionStatus(), eUserLevel);
            MemoryStream memoryStream = new MemoryStream();
            FileStream fileStream = new FileStream(path, FileMode.Open);
            byte[] bytes = new byte[fileStream.Length];
            fileStream.Read(bytes, 0, (int)fileStream.Length);
            memoryStream.Write(bytes, 0, (int)fileStream.Length);
            fileStream.Close();
            BinaryFormatter formatter = new BinaryFormatter();
            memoryStream.Position = 0;
            var result = (Dictionary<ModuleItem, List<PermissionItem>>)formatter.Deserialize(memoryStream);
            memoryStream.Close();
            return result;
        }

        public void SavePermissions(Dictionary<ModuleItem, List<PermissionItem>> permissions,EUserLevel eUserLevel)
        {
            MemoryStream memoryStream = new MemoryStream();
            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(memoryStream, permissions);
            FileStream fileStream  = new FileStream(Path.Combine(SavePath, $"{eUserLevel}Permission.bin"), FileMode.OpenOrCreate);
            BinaryWriter writer = new BinaryWriter(fileStream);
            writer.Write(memoryStream.ToArray());
            fileStream.Close();
            writer.Close();
        }
    }

    [Serializable]
    public class PermissionItem
    {
        public PermissionItem() { }

        public PermissionItem(string itemName, string itemPrompt)
        {
            ItemName = itemName;
            ItemPrompt = itemPrompt;
        }

        public string ItemName { get; set; }

        public string ItemPrompt { get; set; }

        public bool IsEnable { get; set; }

        public override bool Equals(object obj)
        {
            return obj is PermissionItem item &&
                   ItemName == item.ItemName;
        }

        public override int GetHashCode()
        {
            int hashCode = 1258216799;
            hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(ItemName);
            return hashCode;
        }
    }

    [Serializable]
    public class ModuleItem
    {
        public ModuleItem() { }

        public ModuleItem(string itemName, string describe)
        {
            ItemName = itemName;
            Describe = describe;
        }

        public string ItemName { get; set; }

        public string Describe { get; set; }

        public override bool Equals(object obj)
        {
            return obj is ModuleItem item &&
                   ItemName == item.ItemName;
        }

        public override int GetHashCode()
        {
            int hashCode = 1594373205;
            hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(ItemName);
            return hashCode;
        }
    }

    [Serializable]
    public class SerializableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, IXmlSerializable
    {
        public SerializableDictionary() { }
        public SerializableDictionary(Dictionary<TKey, TValue> keyValuePairs)
        {
            foreach (var item in keyValuePairs)
            {
                Add(item.Key, item.Value);
            }

        }
        public void WriteXml(XmlWriter writer)       // Serializer
        {
            XmlSerializer KeySerializer = new XmlSerializer(typeof(TKey));
            XmlSerializer ValueSerializer = new XmlSerializer(typeof(TValue));

            writer.WriteStartElement("SerializableDictionary");
            foreach (KeyValuePair<TKey, TValue> kv in this)
            {
                writer.WriteStartElement("key");
                KeySerializer.Serialize(writer, kv.Key);
                writer.WriteEndElement();
                writer.WriteStartElement("value");
                ValueSerializer.Serialize(writer, kv.Value);
                writer.WriteEndElement();
            }
            writer.WriteEndElement();
            writer.Flush();
            writer.Close();
        }
        public void ReadXml(XmlReader reader)       // Deserializer
        {
            reader.Read();
            XmlSerializer KeySerializer = new XmlSerializer(typeof(TKey));
            XmlSerializer ValueSerializer = new XmlSerializer(typeof(TValue));

            reader.ReadStartElement("SerializableDictionary");
            while (reader.NodeType != XmlNodeType.EndElement)
            {
                reader.ReadStartElement("key");
                TKey tk = (TKey)KeySerializer.Deserialize(reader);
                reader.ReadEndElement();
                reader.ReadStartElement("value");
                TValue vl = (TValue)ValueSerializer.Deserialize(reader);
                reader.ReadEndElement();
                Add(tk, vl);
                reader.MoveToContent();
            }
            reader.ReadEndElement();
            reader.Close();
        }

        XmlSchema IXmlSerializable.GetSchema()
        {
            return null;
        }
    }
}
